use crate::runtime::{Attr, THREADS_MASK, THREADS_MAX};
use core::sync::atomic::{
    AtomicU32,
    Ordering::{self, Acquire, Relaxed, Release},
};

#[repr(C)]
pub(crate) struct AtomicStatus {
    wcnt: AtomicU32,
    rcnt: AtomicU32,
    pcnt: u64,
    stat: AtomicU32,
    worker: u16,
    wexp: u8,
    wexp_next: u8,
    pri: u8,
    is_yield: bool,
}

pub(crate) const STAT_RUN: u32 = 0;
pub(crate) const STAT_JOIN: u32 = 1;
pub(crate) const STAT_RETURN: u32 = 2;
pub(crate) const STAT_FINISH: u32 = 3;
pub(crate) const STAT_ABORT: u32 = 4;
pub(crate) const STAT_EXIT: u32 = 5;

impl AtomicStatus {
    pub(crate) fn new(attr: &Attr) -> Self {
        Self {
            rcnt: AtomicU32::new(1),
            wcnt: AtomicU32::new(1),
            stat: AtomicU32::new(STAT_RUN),
            wexp: 1,
            wexp_next: 1,
            pri: attr.priority,
            worker: 0,
            pcnt: 0,
            is_yield: false,
        }
    }

    pub(crate) fn yield_now(&mut self) {
        self.is_yield = true;
    }

    // wait——all接口使用，目的尽可能减少task空转
    // 这是在Future::poll时才被调用，之后框架一定会调用test_dec_wake.
    // test_dec_wake和test_inc_wake会有并发，利用release/acquire，可以消除wexp在两者间的数据竞争
    pub(crate) fn set_wake_expect(&mut self, wexp: u8) {
        // #18 issue:
        // 不能在运行期间修改wexp，因为它可能导致test_inc_wake返回true，这样正在运行的task又被调度了
        // 因此先存起来，在调度完毕后一定会执行的test_dec_wake中设置，才能保证不存在任何并发冲突.
        self.wexp_next = wexp;
    }

    // test_dec_wake仅在task调度返回Pending的时候才调用，此时test_inc_wake一定返回false
    // 如果这里返回true，则再次进入调度队列
    pub(crate) fn test_dec_wake(&mut self, cnt: u32) -> bool {
        if !self.is_yield {
            return self.test_dec_wake_do(cnt);
        }
        self.is_yield = false;
        true
    }

    fn test_dec_wake_do(&mut self, cnt: u32) -> bool {
        if self.wexp != self.wexp_next {
            self.wexp = self.wexp_next;
            self.wexp_next = 1;
        }
        let cnt = self.wcnt.fetch_sub(cnt, Release) - cnt;
        // self.wexp只在当前线程被修改，一定可以读取到最新值
        cnt >= self.wexp as u32
    }
    pub(crate) fn test_inc_wake(&self) -> bool {
        let cnt = self.wcnt.fetch_add(1, Acquire);
        cnt == self.wexp as u32 - 1
    }

    // 在调用Future::poll之前先获取当前wake_count，作为poll之后test_dec_wake的输入参数，表示本次消耗的次数
    pub(crate) fn wake_count(&self) -> u32 {
        self.wcnt.load(Relaxed)
    }

    // 实际只用到62位，不必考虑溢出
    pub(crate) fn poll_inc(&mut self) {
        self.pcnt += 1;
    }

    pub(crate) fn poll_cnt(&self) -> u64 {
        self.pcnt
    }

    pub(crate) fn inc_ref(&self) {
        // 同一个线程是没有问题的，如果跨线程，跨线程通信的机制一定有Release机制保证另一个线程读取最新数据
        let _ = self.rcnt.fetch_add(1, Relaxed);
    }

    pub(crate) fn test_dec_ref(&self) -> bool {
        // 同一个线程是没有问题的，如果跨线程，跨线程通信的机制一定有Release机制保证另一个线程读取最新数据
        self.rcnt.fetch_sub(1, Relaxed) == 1
    }

    pub(crate) fn status(&self, order: Ordering) -> u32 {
        self.stat.load(order)
    }

    pub(crate) fn set_status(&self, new: u32, order: Ordering) {
        self.stat.store(new, order);
    }

    pub(crate) fn cmp_xchg_status(
        &self,
        current: u32,
        new: u32,
        set_order: Ordering,
    ) -> Result<u32, u32> {
        self.stat.compare_exchange(current, new, set_order, Relaxed)
    }

    pub(crate) fn priority(&self) -> u8 {
        self.pri
    }

    // worker: THREAD_MAX == 1024(0x0400), THREAD_MASK == 0x03FF
    // 0x8000: 表示spawn_local调度，绑定当前worker， future不支持Send场景
    // 0x4000: 表示临时绑定当前worker，适用于资源和scheduler挂钩场景，future是支持Send的
    pub(crate) fn get_local(&self) -> Option<u16> {
        if self.worker >= THREADS_MAX as u16 {
            return Some(self.worker & THREADS_MASK as u16);
        }
        None
    }
    pub(crate) fn set_local(&mut self, id: u16) {
        self.worker = id | 0x8000;
    }

    // Or操作可能一次调度有多个Future调用freeze_local/unfreeze_local，因此需要用计数机制
    pub(crate) fn freeze_local(&mut self, id: u16) {
        self.worker += 0x0400;
        self.worker |= id;
    }
    pub(crate) fn unfreeze_local(&mut self) {
        self.worker -= 0x0400;
        if self.worker < THREADS_MAX as u16 {
            self.worker = 0;
        }
    }
}
